/*
* =====================================================================================
*
*       Filename:  hanvon-libusb.c
*
*    Description:  libusb Hanvon tablet driver    
*
*        Version:  0.1 
*        Created:  08/17/2020 04:05:14 PM
*       Revision:  none
*       Compiler:  gcc
*
*  Maintained by:  scuti@teknik.io
*                  surkeh@protonmail.com
*
* =====================================================================================
*/

#define DEBUG(msg,...) fprintf(stderr,"%s(%d): " msg , __FILE__,__LINE__,__VA_ARGS__)

#include <stdlib.h>
#include <stdio.h>
#include <libusb-1.0/libusb.h>

#include <libevdev/libevdev.h>
#include <libevdev/libevdev-uinput.h>

#define STATE_SUCCESS           0
#define STATE_NOT_FOUND         1

#define VENDOR_ID_HANVON        0x0b57
#define PRODUCT_ID_AM3M         0x8528
#define PRODUCT_ID_AM0806       0x8502
#define PRODUCT_ID_AM0605       0x8503
#define PRODUCT_ID_AM1107       0x8505
#define PRODUCT_ID_AM1209       0x8501
#define PRODUCT_ID_RL0604       0x851f
#define PRODUCT_ID_RL0504       0x851d
#define PRODUCT_ID_GP0806       0x8039
#define PRODUCT_ID_GP0806B      0x8511
#define PRODUCT_ID_GP0605       0x8512
#define PRODUCT_ID_GP0605A      0x803a
#define PRODUCT_ID_GP0504       0x8037
#define PRODUCT_ID_NXS1513      0x8030
#define PRODUCT_ID_GP0906       0x8521
#define PRODUCT_ID_APPIV0906    0x8532

#define AM_PACKET_LEN           10
#define AM_RESOLUTION           40
#define AM_WHEEL_THRESHOLD      4

#define AM_MAX_ABS_X            0x27DE
#define AM_MAX_ABS_Y            0x1CFE
#define AM_MAX_TILT_X           0x3F
#define AM_MAX_TILT_Y           0x7F
#define AM_MAX_PRESSURE         0x400

#define APPIV_MAX_ABS_X         0x5750 
#define APPIV_MAX_ABS_Y         0x5750

#define BUTTON_EVENT_GP         0x01 
#define PEN_EVENT               0x02
#define BUTTON_EVENT_0906       0x0C


static int lbuttons[]={BTN_0,BTN_1,BTN_2,BTN_3};   /* reported on all AMs */
static int rbuttons[]={BTN_4,BTN_5,BTN_6,BTN_7};   /* reported on AM1107+ */

struct hanvon_message {
    unsigned char msgtype;
    unsigned char is_move;
    unsigned short x_movement;
    unsigned short y_movement;
    unsigned char pressure;
    unsigned char x_tilt;
    unsigned char y_tilt;
};

// GLOBAL
int wheel_position; 

int find_device(libusb_device **list, unsigned int count) {
    if (count < 0) {
        return -1;
    }
    int found = -1;
    struct libusb_device_descriptor desc;
    for (unsigned int i = 0; i < count; i++) {
        libusb_device *t = list[i];
        libusb_get_device_descriptor(list[i], &desc);
        if (desc.idVendor == VENDOR_ID_HANVON) {
            switch(desc.idProduct) {
                default:
                    break;
                case PRODUCT_ID_AM0806:
                case PRODUCT_ID_AM0605:
                case PRODUCT_ID_AM1107:
                case PRODUCT_ID_AM1209:
                case PRODUCT_ID_RL0604:
                case PRODUCT_ID_RL0504:
                case PRODUCT_ID_GP0806:
                case PRODUCT_ID_GP0806B:
                case PRODUCT_ID_GP0605:
                case PRODUCT_ID_GP0605A:
                case PRODUCT_ID_GP0504:
                case PRODUCT_ID_NXS1513:
                case PRODUCT_ID_GP0906:
                case PRODUCT_ID_APPIV0906:
                    return i;
            } // end switch
        } // end if
    } // end for 
    return found;
}

void display_packets(const unsigned char* buf) {
    for(int i = 0; i < AM_PACKET_LEN; i++) {
        fprintf(stderr,"0x%x, ", buf[i]);
    }
    fprintf(stderr,"\r");
}

void callback(struct libusb_transfer *transfer) {
    unsigned char *data = transfer -> buffer;
    display_packets(data);
}

static inline void report_buttons( struct libevdev_uinput *ud, 
                                   int buttons[], 
                                   unsigned char data)
{
    int err = 0;
    if((data & 0xf0) == 0xa0) {
        // TODO test that these are the correct buttons and all buttons are covered
        err = libevdev_uinput_write_event(ud, EV_KEY, buttons[1], (data & 0x02)); 
        if(err) { DEBUG("err: %d\n",err); }
        err = libevdev_uinput_write_event(ud, EV_KEY, buttons[2], (data & 0x04)); 
        if(err) { DEBUG("err: %d\n",err); }
        err = libevdev_uinput_write_event(ud, EV_KEY, buttons[3], (data & 0x08)); 
        if(err) { DEBUG("err: %d\n",err); }
    } else if(data <= 0x3f) {   /* slider area active */
        int delta = data - wheel_position;
        if(abs(delta) < AM_WHEEL_THRESHOLD) {
            err = libevdev_uinput_write_event(ud, EV_REL, REL_WHEEL, delta); // TODO test delta as input
            if(err) { DEBUG("err: %d\n",err); }
            wheel_position = data;
        }

    }
}


// NOTE:
// Judging by the original driver, this should work for all but may not work
// for the APPIV0906. Possibly needs little endian for APPIV0906 x and y data
// but we don't have any means of testing this without that tablet.
// NOTE:
// Left and right mouse click should work for all but additional buttons are 
// not supported by the default handler.
void callback_default (struct libusb_transfer *tx) { // for callback
    unsigned char *data = tx -> buffer;
    struct hanvon_message *msg = (struct hanvon_message *)tx -> buffer;
    int err = 0;
    struct libevdev_uinput *ud = tx -> user_data;
    switch(msg->msgtype) {
        case BUTTON_EVENT_GP:
            if(data[1] == 0x55) { // left side buttons
	        report_buttons(ud, lbuttons, msg->x_movement); // button pressed data in same place as position data
	    }

	    if(data[3] == 0xAA) { // right side buttons (am1107, am1209
                report_buttons(ud, rbuttons, msg->y_movement); // button pressed data in same place as position data
	    }
            break;
        case PEN_EVENT:
            /* is_move values:
                0x80: near,      0x02: button press
                0x10: floating,  0x01: touching */
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_TOOL_PEN, msg->is_move & (0x80|0x10)
            );
            if(err) { DEBUG("err: %d\n",err); }

            if(msg->is_move & (0x80|0x10)) {
                msg->x_movement = htobe16(msg->x_movement);
                //DEBUG("Set X to %x\n",msg->x_movement);
                err = libevdev_uinput_write_event(
                    ud, EV_ABS, ABS_X, msg->x_movement
                );
                if(err) { DEBUG("err: %d\n",err); }
                msg->y_movement = htobe16(msg->y_movement);
                //DEBUG("Set Y to %x\n",msg->y_movement);
                err = libevdev_uinput_write_event(
                    ud, EV_ABS, ABS_Y, msg->y_movement
                );
                if(err) { DEBUG("err: %d\n",err); }
            }
            err = libevdev_uinput_write_event(
                ud, EV_ABS, ABS_PRESSURE, msg->pressure * 8 // reference original driver
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_ABS, ABS_TILT_X, msg->x_tilt
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_ABS, ABS_TILT_Y, msg->y_tilt
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_LEFT, msg->is_move & 0x01
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_RIGHT, (msg->is_move & 0x02) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            // data[1]:
            // 0x10 = lift,         0x90 = close,           0x91 = press
            // 0x12 = btn (lift),   0x92 = btn (close),     0x93 = btn (press)
            break;
        case BUTTON_EVENT_0906:
            // TODO confirm this is the byte that contains button flags
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_0, (msg->is_move & 0x0100) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_1, (msg->is_move & 0x0200) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_2, (msg->is_move & 0x0400) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_3, (msg->is_move & 0x0800) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_4, (msg->is_move & 0x1000) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_5, (msg->is_move & 0x2000) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_6, (msg->is_move & 0x4000) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
            err = libevdev_uinput_write_event(
                ud, EV_KEY, BTN_7, (msg->is_move & 0x8000) / 2
            );
            if(err) { DEBUG("err: %d\n",err); }
        default:
            // do nothing
            break;
    }
    // always display packets 
    display_packets(data);

    err += libevdev_uinput_write_event(ud, EV_SYN, SYN_REPORT, 0);
    if (err != 0) {
        printf("error : gp0504, %i\n", err);
    }
    return;
}

// https://www.freedesktop.org/software/libevdev/doc/latest/group__kernel.html
int init_ctrl(struct libusb_device * const d, 
            struct libevdev **evdev, 
            struct libevdev_uinput **uidev) {
    struct input_absinfo *abs;
    printf("init_ctrl: %x\n", uidev);
    wheel_position = AM_WHEEL_THRESHOLD - 1; // init global
    if (d == NULL) {
        return -1;
    }
    int is_ok = 0;
    struct libusb_device_descriptor desc;
    libusb_get_device_descriptor(d, &desc);
    (*evdev) = libevdev_new();

    // set up inputs all devices have
    libevdev_enable_property((*evdev), INPUT_PROP_DIRECT);
    libevdev_enable_event_type((*evdev), EV_SYN);
    libevdev_enable_event_code((*evdev), EV_SYN, SYN_REPORT, NULL);
    libevdev_enable_event_type((*evdev), EV_KEY); // enable pen button
    libevdev_enable_event_code((*evdev), EV_KEY, BTN_TOOL_PEN, NULL);
    libevdev_enable_event_code((*evdev), EV_KEY, BTN_LEFT, NULL);  // pen tap 
    libevdev_enable_event_code((*evdev), EV_KEY, BTN_RIGHT, NULL); // pen button
    // enable absolute position, pressure, tilt
    libevdev_enable_event_type((*evdev), EV_ABS); 
    abs = malloc(sizeof(struct input_absinfo));
    // set up absolute x coordinate input
    abs->value = 0x1000;
    abs->minimum = 0;
    switch(desc.idProduct) {
        case PRODUCT_ID_APPIV0906:
            abs->maximum = APPIV_MAX_ABS_X;
            break;
        default:
            abs->maximum = AM_MAX_ABS_X;
            break;
    }
    abs->fuzz = 0;
    abs->flat = 0;
    abs->resolution = AM_RESOLUTION;
    if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_X, abs)<0) {
        DEBUG("%s","failed to register absolute x\n");
        is_ok = -1;
    }
    // set up absolute y coordinate input
    abs->value = 0x1000;
    abs->minimum = 0;
    switch(desc.idProduct) {
        case PRODUCT_ID_APPIV0906:
            abs->maximum = APPIV_MAX_ABS_Y;
            break;
        default:
            abs->maximum = AM_MAX_ABS_Y;
            break;
    }
    abs->resolution = AM_RESOLUTION;
    if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_Y, abs)<0) {
        DEBUG("%s","failed to register absolute y\n");
        is_ok = -1;
    }
    // set up pressure input
    abs -> value = 0;
    abs -> minimum = 0;
    abs -> maximum = AM_MAX_PRESSURE;
    abs -> resolution = 0;
    if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_PRESSURE, abs)<0) {
        DEBUG("%s","failed to register pressure\n");
        is_ok = -1;
    }
    // set up tilt x input
    abs -> value = 0;
    abs -> minimum = 0;
    abs -> maximum = AM_MAX_TILT_X;
    abs -> resolution = 0;
    if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_TILT_X, abs)<0) {
        DEBUG("%s","failed to register x tilt\n");  
        is_ok = -1;
    }
    // set up tilt y input
    abs -> value = 0;
    abs -> minimum = 0;
    abs -> maximum = AM_MAX_TILT_Y;
    abs -> resolution = 0;
    if(libevdev_enable_event_code((*evdev), EV_ABS, ABS_TILT_Y, abs)<0) {
        DEBUG("%s","failed to register y tilt\n");
        is_ok = -1;
    }

    // Scroll wheel is NOT universal
    if(libevdev_enable_event_code((*evdev), EV_REL, REL_WHEEL, NULL)<0) {
        DEBUG("%s","failed to register scroll wheel\n");
        is_ok = -1;
    }

    // set up device-specific inputs
    switch(desc.idProduct) {
        case PRODUCT_ID_AM3M:
        case PRODUCT_ID_AM0806:
        case PRODUCT_ID_AM0605:
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_0, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_1, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_2, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_3, NULL);
            break;
        case PRODUCT_ID_AM1107:
        case PRODUCT_ID_AM1209:
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_0, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_1, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_2, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_3, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_4, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_5, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_6, NULL);
            libevdev_enable_event_code((*evdev), EV_KEY, BTN_7, NULL);
            break;
        default:
            // do nothing
            break;
   }

    // set up libevdev device name strings
    switch(desc.idProduct) {
        // cases are in ID order
        case PRODUCT_ID_AM3M:
            // space between Art and Master intentional
            libevdev_set_name((*evdev), "Hanvon Art Master III");
            break;
        case PRODUCT_ID_AM0806:
            libevdev_set_name((*evdev), "Hanvon ArtMaster AM0806");
            break;
        case PRODUCT_ID_AM0605:
            libevdev_set_name((*evdev), "Hanvon ArtMaster AM0605");
            break;
        case PRODUCT_ID_AM1107:
            // space between Art and Master intentional
            libevdev_set_name((*evdev), "Hanvon Art Master AM1107");
            break;
        case PRODUCT_ID_AM1209:
            libevdev_set_name((*evdev), "Hanvon ArtMaster AM1209");
            break;
        case PRODUCT_ID_RL0604:
            libevdev_set_name((*evdev), "Hanvon Rollick 0604");
            break;
        case PRODUCT_ID_RL0504:
            libevdev_set_name((*evdev), "Hanvon Rollick 0504");
            break;
        case PRODUCT_ID_GP0806:
            libevdev_set_name((*evdev), "Hanvon Graphicpal 0806");
            break;
        case PRODUCT_ID_GP0806B:
            libevdev_set_name((*evdev), "Hanvon Graphicpal 0806B");
            break;
        case PRODUCT_ID_GP0605:
            libevdev_set_name((*evdev), "Hanvon Graphicpal 0605");
            break;
        case PRODUCT_ID_GP0605A:
            libevdev_set_name((*evdev), "Hanvon Graphicpal 0605A");
            break;
        case PRODUCT_ID_GP0504:
            libevdev_set_name((*evdev), "Hanvon Graphicpal 0504");
            break;
        case PRODUCT_ID_NXS1513:
            libevdev_set_name((*evdev), "Hanvon Nilox NXS1513");
            break;
        case PRODUCT_ID_GP0906:
            libevdev_set_name((*evdev), "Hanvon Graphicpal 0906");
            break;
        case PRODUCT_ID_APPIV0906:
            libevdev_set_name((*evdev), "Hanvon Art Painter Pro APPIV0906");
            break;
    }

    int err = libevdev_uinput_create_from_device(
        (*evdev), LIBEVDEV_UINPUT_OPEN_MANAGED, uidev
    );
    printf("Initializing controls status: %x, \n", uidev);
    free(abs);
    return is_ok;
}

// GLOBAL: for main and hotplug_callback
static int HOT_COUNT = 0; 
static struct libusb_transfer *TX = NULL;
void loop() {
    int status = 0;
    int LAST_HOT_COUNT = 0;
    while (1) {
        if (TX != NULL && TX->dev_handle != 0) {
            status = libusb_submit_transfer(TX);
        }
        if (status < 0 && LAST_HOT_COUNT != HOT_COUNT) { 
            printf("\nwarning: usb transfer status = %i\n", status);
            //continue;
        }
        LAST_HOT_COUNT = HOT_COUNT;
        libusb_handle_events_completed(NULL, NULL);
    }
}

// TODO: make scuti explain why this is called twice
int hotplug_callback(struct libusb_context *ctx, struct libusb_device *dev,
                     libusb_hotplug_event event, void *user_data) {

    static libusb_device_handle *dev_handle;
    static struct libevdev_uinput  *uidev = NULL;
    static struct libevdev         *evdev = NULL;
    struct libusb_device_descriptor desc;
    int rc;
    libusb_get_device_descriptor(dev, &desc);
    if (LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED == event) {
        // check if the device supported
        libusb_device *devs[1] = {dev};
        int index = find_device(devs, 1);
        if (index < 0) {
            printf("INFO: Detected Hanvon device, but it is not supported.\n");
        } else {
            // libusb functions that accept libusb_device = SAFE
            // submit asynchronous transfers              = SAFE
            // other functions that accept device handle  = NOT SAFE
            libusb_open(dev, &dev_handle);
            if (init_ctrl(dev, &evdev, &uidev) < 0) {
                printf("Error: Could not initialize controls.\n");
                exit(EXIT_FAILURE);
                
            }
            const int ENDPOINT_ADDR = 0x81;  // bEndpointAddress from lsusb -v
            //AM_PACKET_LEN = 10;            // wMaxPacketSize from lsusb -v
            unsigned char buffer[AM_PACKET_LEN];
            TX = libusb_alloc_transfer(0);
            libusb_fill_interrupt_transfer(
                    TX,
                    dev_handle, 
                    ENDPOINT_ADDR, 
                    buffer, 
                    AM_PACKET_LEN,
                    callback_default,
                    uidev,     // extra data to send in tx    
                    130        // timeout in milliseconds
            );
        }
    } else if (LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT) {
        if (dev_handle) {
            libusb_close(dev_handle);
            dev_handle = NULL;
            libevdev_uinput_destroy(uidev);
            libusb_free_transfer(TX);
            TX = NULL;
        }
    } else {
        printf ("INFO: Device left, but handle did not exist.\n");
    }
    HOT_COUNT++;
    return 0;
}

int main() {
#define UNREF_DEVICE 1 
#define KEEP_DEVICE_REF 0 

    int r = libusb_init(NULL);
    if (r < 0) {
        return r;
    } 
    libusb_hotplug_callback_handle callback_handle;
    int rc;
    rc = libusb_hotplug_register_callback(
        NULL, 
        LIBUSB_HOTPLUG_EVENT_DEVICE_ARRIVED |
        LIBUSB_HOTPLUG_EVENT_DEVICE_LEFT, 
        0, 
        VENDOR_ID_HANVON,         // VENDOR
        LIBUSB_HOTPLUG_MATCH_ANY, // PRODUCT
        LIBUSB_HOTPLUG_MATCH_ANY, // DEV CLASS
        hotplug_callback, 
        NULL,
        &callback_handle
    );
    if (rc != LIBUSB_SUCCESS) {
        printf("Error creating a hotplug callback\n");
        libusb_exit(NULL);
        return EXIT_FAILURE;
    }
    loop();
    return 0;
}
